import { download } from '@/api/doc'
import Vue from 'vue'
import fs from 'fs'
import request from 'request'
import mkdirp from 'mkdirp'
import path from 'path'
import axios from 'axios'
import { suffix } from '@/utils/path'
export interface TransportItem {
  docId: number;

  docName: string;

  localPath: string;

  downloadId: string;

  done: number;

  size: number;

  lastDone?: number;

  chunkNum?: number;

  speed?: number;

  // request?: request.Request;

  errMsg?: string;
}

export type DownloaderStatus = 'pending' | 'downloading' | 'success' | 'fail' | 'pause';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const httpAdapter = require('axios/lib/adapters/http.js')

// console.log('httpAdapter', httpAdapter)

const http = axios.create({
  adapter: httpAdapter,
  timeout: 30 * 1000
})

export default class Transporter {
  maxThreadNum = 5

  pendingQueue: TransportItem[] = []

  downloadingQueue: TransportItem[] = []

  doneQueue: TransportItem[] = []

  errorQueue: TransportItem[] = []

  pauseQueue: TransportItem[] = []

  resumeQueue: TransportItem[] = []

  constructor () {
    this.run()
  }

  run () {
    const fn = () => {
      this.doDownload()
    }
    const handler = setInterval(fn, 100)
    this.speedRun()
  }

  removeByRoot (rootDocId: number) {
    // this.queue = this.queue.filter(v => v.rootDocId !== rootDocId)
    // const items = this.queue.filter(v => v.rootDocId === rootDocId && v.request)
    // items.forEach(v => {
    //   if (v.request) v.request.abort()
    // })
    // this.downloadingQueue = this.downloadingQueue.filter(v => v.rootDocId !== rootDocId)
  }

  download (item: TransportItem) {
    this.pendingQueue.splice(0, 0, item)
  }

  getStatus (downloadId: string): DownloaderStatus {
    if (this.downloadingQueue.some(v => v.downloadId === downloadId)) return 'downloading'
    if (this.pendingQueue.some(v => v.downloadId === downloadId)) return 'pending'
    if (this.resumeQueue.some(v => v.downloadId === downloadId)) return 'pending'
    if (this.pauseQueue.some(v => v.downloadId === downloadId)) return 'pause'
    if (this.errorQueue.some(v => v.downloadId === downloadId)) return 'fail'
    if (this.doneQueue.some(v => v.downloadId === downloadId)) return 'success'
    return 'pending'
  }

  getProgress (downloadId: string): { done: number; total: number } {
    const total = [
      ...this.pendingQueue,
      ...this.downloadingQueue,
      ...this.pauseQueue,
      ...this.doneQueue,
      ...this.resumeQueue,
      ...this.errorQueue]
      .filter(v => v.downloadId === downloadId)
      .reduce((t, n) => {
        return t + n.size
      }, 0)
    const done = [
      ...this.doneQueue,
      ...this.downloadingQueue,
      ...this.resumeQueue,
      ...this.pauseQueue,
      ...this.errorQueue]
      .filter(v => v.downloadId === downloadId)
      .reduce((t, n) => {
        return t + n.done
      }, 0)
    return { total, done }
  }

  pause (downloadId: string) {
    const items = [...this.downloadingQueue, ...this.pendingQueue, ...this.resumeQueue].filter(v => v.downloadId === downloadId)
    this.pauseQueue.push(...items)
    this.pendingQueue = this.pendingQueue.filter(v => v.downloadId !== downloadId)
    this.resumeQueue = this.resumeQueue.filter(v => v.downloadId !== downloadId)
    this.downloadingQueue = this.downloadingQueue.filter(v => v.downloadId !== downloadId)
  }

  resume (downloadId: string) {
    const items = this.pauseQueue.filter(v => v.downloadId === downloadId)
    // items.forEach(v => {
    //   if (v.request) v.request.resume()
    // })
    this.resumeQueue.push(...items)
    this.pauseQueue = this.pauseQueue.filter(v => v.downloadId !== downloadId)
  }

  retry (downloadId: string) {
    const items = this.errorQueue.filter(v => v.downloadId === downloadId)
    // items.forEach(v => {
    //   if (v.request) v.request.resume()
    // })
    if (items.length < 1) return
    this.resumeQueue.push(...items)
    this.errorQueue = this.errorQueue.filter(v => v.downloadId !== downloadId)
  }

  pauseItem (item: any) {
    const items = this.downloadingQueue.filter(v => v === item)
    if (items.length < 1) return
    this.pauseQueue.push(...items)
    this.downloadingQueue = this.downloadingQueue.filter(v => v !== item)
  }

  resumeItem (item: any) {
    const items = this.pauseQueue.filter(v => v === item)
    if (items.length < 1) return
    // items.forEach(v => {
    //   if (v.request) v.request.resume()
    // })
    this.resumeQueue.push(...items)
    this.pauseQueue = this.pauseQueue.filter(v => v !== item)
  }

  retryItem (item: any) {
    const items = this.errorQueue.filter(v => v === item)
    if (items.length < 1) return
    // items.forEach(v => {
    //   if (v.request) v.request.resume()
    // })
    this.resumeQueue.push(...items)
    this.errorQueue = this.errorQueue.filter(v => v !== item)
  }

  abort (downloadId: string) {
    const items = this.downloadingQueue.filter(v => v.downloadId === downloadId)
    // items.forEach(v => {
    //   if (v.request) v.request.abort()
    // })
    this.pendingQueue = this.pendingQueue.filter(v => v.downloadId !== downloadId)
    this.downloadingQueue = this.downloadingQueue.filter(v => v.downloadId !== downloadId)
    this.pauseQueue = this.pauseQueue.filter(v => v.downloadId !== downloadId)
    this.resumeQueue = this.resumeQueue.filter(v => v.downloadId !== downloadId)
    this.errorQueue = this.errorQueue.filter(v => v.downloadId !== downloadId)
  }

  remove (downloadId: string) {
    this.abort(downloadId)
  }

  removeAll () {
    // [...this.pauseQueue, ...this.downloadingQueue, ...this.resumeQueue].forEach(v => {
    //   if (v.request) v.request.abort()
    // })
    this.pendingQueue = []
    this.downloadingQueue = []
    this.pauseQueue = []
    this.resumeQueue = []
    this.errorQueue = []
  }

  private doDownload () {
    if (this.downloadingQueue.length >= this.maxThreadNum) return
    const item = this.resumeQueue.length > 0 ? this.resumeQueue.pop() : this.pendingQueue.pop()
    if (!item) return
    this.downloadingQueue.push(item)
    const fn = () => {
      let idx = this.downloadingQueue.findIndex(v => v === item)
      if (idx >= 0) this.downloadingQueue.splice(idx, 1)
      else {
        idx = this.pauseQueue.findIndex(v => v === item)
        if (idx >= 0) this.pauseQueue.splice(idx, 1)
        else {
          idx = this.resumeQueue.findIndex(v => v === item)
          if (idx >= 0) this.resumeQueue.splice(idx, 1)
        }
      }
    }
    download(item.docId, true).then(url => {
      const s = suffix(url) !== suffix(item.docName) ? `.${suffix(url)}` : ''
      const response = {
        'content-disposition': `attachment; filename=${encodeURIComponent(item.docName + s)}`
      }
      return Vue.prototype.$oss.signatureUrl(url, { response }).then((url: string) => {
        const fn = (size: number) => {
          item.done += size
        }
        // return this.downloadWidthStream(item, url, item.localPath, fn)
        return this.downloadWidthSlice(item, url, item.localPath + s, fn)
      })
    }).then(() => {
      this.doneQueue.push(item)
      fn()
    }).catch((err) => {
      if (!err?.abort) {
        item.errMsg = err?.message || err
        this.errorQueue.push(item)
        fn()
      }
    })
  }

  private downloadWidthSlice (item: TransportItem, url: string, p: string, progress?: (size: number) => void): Promise<{ path: string }> {
    return new Promise((resolve, reject) => {
      mkdirp.sync(path.resolve(p, '..'))

      const stream = fs.createWriteStream(p, { flags: (item.chunkNum || 0) > 0 ? 'a' : 'w' })
      const download = () => {
        if (!this.downloadingQueue.includes(item)) {
          stream.close()
          // eslint-disable-next-line prefer-promise-reject-errors
          reject({ abort: true } as any)
          return
        }
        if (item.done >= item.size) {
          stream.close()
          resolve({ path: p })
        } else {
          const chunkNum = item.chunkNum || 0

          http.get(url, { params: { chunkNum }, responseType: 'blob' }).then(res => {
            const data = res.data as Blob
            if (data.size < 1) {
              stream.close()
              resolve({ path: p })
            } else {
              const toBuffer = (blob: Blob) => {
                return blob.arrayBuffer().then(r => {
                  const buf = Buffer.alloc(r.byteLength)
                  const view = new Uint8Array(r)
                  for (let i = 0; i < buf.length; ++i) {
                    buf[i] = view[i]
                  }
                  return buf
                })
              }
              toBuffer(data).then(r => {
                stream.write(r, (err) => {
                  if (err) {
                    stream.close()
                    reject(err)
                  } else {
                    item.chunkNum = (item.chunkNum || 0) + 1
                    if (progress) progress(data.size)
                    download()
                  }
                })
              })
            }
          }).catch((err: any) => {
            stream.close()
            reject(err)
          })
        }
      }

      download()
    })
  }

  private downloadWidthStream (item: TransportItem, url: string, p: string, progress?: (size: number) => void): Promise<{ path: string }> {
    return new Promise((resolve, reject) => {
      mkdirp.sync(path.resolve(p, '..'))
      const stream = fs.createWriteStream(p)
      const fn = () => {
        const idx = this.downloadingQueue.findIndex(v => v === item)
        if (idx >= 0) this.downloadingQueue.splice(idx, 1)
      }
      stream.on('pipe', function (s) {
        s.on('data', chunk => {
          if (progress) progress(chunk.length)
        })
      })
      // stream.addListener('finish', () => {
      //   this.doneQueue.push(item)
      //   fn()
      //   resolve({ path: p })
      // })
      // stream.addListener('error', err => {
      //   this.errorQueue.push(item)
      //   fn()
      //   reject(err)
      // })
      const req = request(url, { timeout: 30 * 1000 })
      // item.request = req
      req.pipe(stream)
      req.on('error', (err) => {
        reject(err)
      })
      req.on('end', () => {
        if (item.done < item.size) {
          reject(new Error('连接中断'))
        } else {
          resolve({ path: p })
        }
      })
    })
  }

  private speedRun () {
    const fn = () => {
      this.downloadingQueue.forEach(v => {
        v.speed = Math.max(0, v.done - (v.lastDone || 0))
        v.lastDone = v.done
      })
    }
    setInterval(fn, 1000)
  }
}
